/**************************************************************************
 * Copyright (C) 2011 Tyler Robertson
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************/


#include "Arduino.h"
#include "SPI/SerialPeripheralInterface.h"
#include "Common/crc16.h"

/*** Helper functions ***/
static void spi_write_packet( packet_type type, const void *packet, unsigned long length )
{
	unsigned short computed_crc = crc16( 0, (const unsigned char*)packet, length );
	spi_write_byte( type );
	spi_write_array( (const unsigned char*)packet, length );
	spi_write_array( (const unsigned char*)&computed_crc, 2 );
	spi_write_byte( PT_END_OF_PACKET );
}

static bool spi_read_packet( void *packet, unsigned long length )
{	
	unsigned short received_crc = 0;
	unsigned short computed_crc = 0;
	
	// Read the packet
	spi_read_array( (unsigned char*)packet, length );
	
	// Read the crc
	spi_read_array( (unsigned char*)&received_crc, 2 );
	
	// Do a local checksum
	computed_crc = crc16( 0, (const unsigned char*)packet, length );
	
	// Check for corruption
	return ( computed_crc == received_crc && spi_read_byte() == PT_END_OF_PACKET );
}

void spi_initialize()
{
	unsigned char clr = 0;
	
	pinMode( MISO, INPUT  );
	pinMode( MOSI, OUTPUT );
	pinMode( SCK,  OUTPUT );
	pinMode( SS,   OUTPUT );

	SPCR = (1<<SPE) | (1<<MSTR) | /*(1<<SPR1) |*/ (1<<SPR0)	;
	
	clr = SPDR;
		
	delay( 10 );
	
	Serial.println( "SPI Initialized" );	
}

unsigned char spi_transfer_byte( volatile unsigned char data )
{
	SPDR = data;
	while( !(SPSR & (1<<SPIF)) );
	return SPDR;	
}	   
	   
unsigned char spi_read_byte()
{
	return spi_transfer_byte( 0 );
}

void spi_write_byte( unsigned char data )
{
 	spi_transfer_byte( data );
}

void spi_read_array( unsigned char *array, unsigned long num_bytes )
{
	for( int i=0; i<num_bytes; ++i )
		array[i] = spi_transfer_byte( 0 );
}

void spi_write_array( const unsigned char *array, unsigned long num_bytes )
{
	for( int i=0; i<num_bytes; ++i )
    {
		spi_transfer_byte( array[i] );
    }
}

void spi_select_slave( unsigned char slave )
{
	// Slaves are active low
	digitalWrite( slave, LOW );
}

void spi_deselect_slave( unsigned char slave )
{
	// Slaves are active low
	digitalWrite( slave, HIGH );
}

void spi_write_packet( const ascii_packet_t *ascii_packet )
{
	// Ascii is special case. Need to serialize and not send pointers
	const unsigned short computed_crc = crc16( 0, (const unsigned char*)ascii_packet->message, ascii_packet->num_characters );
	spi_write_byte( PT_ASCII_PACKET );
	spi_write_array( (const unsigned char*)&ascii_packet->num_characters, 4);
	spi_write_array( (const unsigned char*)ascii_packet->message, ascii_packet->num_characters );
	spi_write_array( (const unsigned char*)&computed_crc, 2 );
	spi_write_byte( PT_END_OF_PACKET );
}

void spi_write_packet( const statistics_packet_t *statistics_packet )
{
	spi_write_packet( PT_STATISTICS_PACKET, statistics_packet, sizeof( statistics_packet_t ) );
}

void spi_write_packet( const timer_op_packet_t *timer_op_packet )
{
	spi_write_packet( PT_TIMER_PACKET, timer_op_packet, sizeof( timer_op_packet_t ) );
}

void spi_write_packet( const command_packet_t *command_packet )
{
	spi_write_packet( PT_COMMAND_PACKET, command_packet, sizeof( command_packet_t ) );														
}

void spi_write_packet( const state_packet_t *state_packet )
{
	spi_write_packet( PT_STATE_PACKET, state_packet, sizeof( state_packet_t ) );
}

bool spi_read_packet( statistics_packet_t *statistics_packet )
{
	return spi_read_packet( (void*)statistics_packet, sizeof( statistics_packet_t ) );
}

bool spi_read_packet( timer_op_packet_t *timer_op_packet )
{
	return spi_read_packet( (void*)timer_op_packet, sizeof( timer_op_packet_t ) );
}

bool spi_read_packet( command_packet_t *command_packet )
{
	return spi_read_packet( (void*)command_packet, sizeof( command_packet_t ) );												
}

bool spi_read_packet( state_packet_t *state_packet )
{
	return spi_read_packet( (void*)state_packet, sizeof( state_packet_t ) );
}
